#!/usr/bin/python
import urllib2, cookielib, mimetools, mimetypes, itertools, simplejson
from xml.dom.minidom import parseString
from xml.etree import ElementTree

""" MuConnection containd all the necisary classes for establishing a secured connection to the Mu 
	Test Suite and will be used by all otehr classes to send and retrieve data from/to the Mu Test
	Suite.
	
	Classes contained:
	MuConnection - uede to establish and maintin a connection, send data, and retrieve data.
	
	MuResponseObject - The response class that is returned by all commands in the this API. It is an 
		attempt to reduce impact if any changes in the underlying API occur.
	
	MultiPartForm - usede for mime attachments on some commands to the Mu and is controled entirely
		by methods in MuConection.
	
"""

class MuConnection():
	""" MuConnection: This class is instantiated to establish a secured connection to the Mu Test Suite.
		It controls the communication of all classes to the Mu Test Suite.

		Arguments: 
		username - the username to use in establishing the connection. default is admin. 
			currenlty only admin can run the API
		password - the password to use in estabishing a secured connection. default is admin.

		Globals:
		
		TODO:
		Need to implement a logging method
	"""
	
	TOP_LEVEL_URL = "https://demo.homedns.org"
	USERNAME = "admin"
	PASSWORD = "admin"
	COOKIES = None
	# create a password manager
	PASSWORD_MNGR = None
	AUTH_HANDLER = None
	# create cookie handler
	COOKIE_HANDLER = None
	OPENER = None
	RESPONSE_OBJECT = None
	VERBOSE = False
	
	def __init__(self, mu="", username="admin", password="admin"):
		""" initiate a new MuConnection for testing 
			
			Arguments:
			Mu - the mu ip address or https URL
			username - the username for authenticatoin. normaly admin
			password - the password for authentication
			
		"""
		if mu.startswith("https://"):
			#looks like an url so go with that
			self.TOP_LEVEL_URL = mu
		elif mu.startswith("http://"):
			#looks like an url so go with that
			self.TOP_LEVEL_URL = mu
		else:
			#we are lazy and just add https:// to front of value to make an url
			self.TOP_LEVEL_URL = "https://" + mu
		self.setConnection(self.TOP_LEVEL_URL, username, password)


	def setAuthInfo(self, baseUrl, username="admin", password="admin"):
		""" used internaly by MuConenction for authentication handaling
			
			Arguments:
			baseUrl - the root url for the Mu: https://1.1.1.1 or https://myMu.myDomain
			username - the username for authenticatoin. normaly admin
			password - the password for authentication
			
		"""
		self.USERNAME = username
		self.PASSWORD = password
		self.PASSWORD_MNGR = urllib2.HTTPPasswordMgrWithDefaultRealm()
		self.PASSWORD_MNGR.add_password(None, baseUrl, username, password)
		# basic auth handler
		self.AUTH_HANDLER = urllib2.HTTPBasicAuthHandler(self.PASSWORD_MNGR)
		return
	
	def setConnection(self, baseUrl, username, password):
		""" used internaly by MuConenction for connection establishment
			
			Arguments:
			baseUrl - the root url for the Mu: https://1.1.1.1 or https://myMu.myDomain
			username - the username for authenticatoin. normaly admin
			password - the password for authentication
			
		"""
		self.setAuthInfo(baseUrl, username, password)
		self.COOKIE_HANDLER = urllib2.HTTPCookieProcessor(self.COOKIES)
		# create "opener" (OpenerDirector instance)
		self.OPENER = urllib2.build_opener(self.AUTH_HANDLER,self.COOKIE_HANDLER)
		#self.OPENER = urllib2.build_opener(self.AUTH_HANDLER)
		# install opener for use with all requests
		urllib2.install_opener(self.OPENER)
		return

	def get(self,url,isJsonResponse=False,userAgent=USERNAME):
		""" Get method used to get html reponses and objects from the Mu API sessions
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			isJsonResponse - is the response in the form of a json object
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject.
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - contains Mu Job UUID if a UUID is found in XML response from Mu
					JOBID - contains Mu Job ID if a Job Id is found in XML response from Mu
					JSON_OBJCT - conains JSON data if isJsonResponse is true
					RETRIEVED_OBJECT - Fomrated Object of type specified in RETRIEVED_OBJECT_TYPE
					RETRIEVED_OBJECT_TYPE - Can be JSON, XML, TXT, ERROR
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - JSON status if JSON response is found
					NAME - contains name value if JSON name or xml name data is found
					MESSAGE - JSON message value if JSON response is found
			
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		req = urllib2.Request(url)
		req.add_header("User-Agent", userAgent)
		self.RESPONSE_OBJECT.setRequest(req)
		try:
			buffer = urllib2.urlopen(req)
			if isJsonResponse:
				try:
					data = buffer.read()
					data = simplejson.loads(data)
				except:
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,False)
					return self.RESPONSE_OBJECT
			else:
				data = buffer.read()
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
		self.RESPONSE_OBJECT.setRawResponse(data)
		self.processResponse(data,self.RESPONSE_OBJECT,isJsonResponse)
		return self.RESPONSE_OBJECT 
			
	def getFile(self,url,fileToSave='',fileMode='b',userAgent=USERNAME):
		""" Get method used to get stream of data  and save in a file
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			fileToSave - file name including path to save data in
			fileMode - default is binary (b) but text (t) can be specified
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - none
					JOBID - none
					JSON_OBJCT - none
					RETRIEVED_OBJECT - File name used in file write operation
					RETRIEVED_OBJECT_TYPE - FILE
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - none
					NAME - none
					MESSAGE - none
			
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		req = urllib2.Request(url)
		req.add_header("User-Agent", userAgent)
		self.RESPONSE_OBJECT.setRequest(req)
		try:
			# Open the local file for writing
			newFile = open(fileToSave, "w" + fileMode)
			buffer = urllib2.urlopen(req)
			#Write to it from url read
			data = buffer.read()
			newFile.write(data)
			#newFile.write(buffer.read())
			newFile.close()
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
			self.RESPONSE_OBJECT.setError(True)
			self.RESPONSE_OBJECT.setRetrievedObjectType(self.RESPONSE_OBJECT.HTTP_ERROR)
			self.RESPONSE_OBJECT.setRetrievedObject(err)
			self.RESPONSE_OBJECT.setCompleted(False)
		self.RESPONSE_OBJECT.setRetrievedObjectType(self.RESPONSE_OBJECT.FILE)
		self.RESPONSE_OBJECT.setRetrievedObject(fileToSave)
		self.RESPONSE_OBJECT.setRawResponse(data)
		self.RESPONSE_OBJECT.setError(False)
		self.RESPONSE_OBJECT.setCompleted(True)
		return self.RESPONSE_OBJECT
		
	def put(self, url, dataToSend, contentType="application/json; charset=utf-8", isJsonResponse = True, userAgent=USERNAME):
		""" PUT method used to send data to the Mu
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			dataToSend - data to send to the Mu normaly in string of JSON or XML data
			contentType - default is "application/json; charset=utf-8"
			isJsonResponse - is the response in the form of a json object
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject.
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - contains Mu Job UUID if a UUID is found in XML response from Mu
					JOBID - contains Mu Job ID if a Job Id is found in XML response from Mu
					JSON_OBJCT - conains JSON data if isJsonResponse is true
					RETRIEVED_OBJECT - Fomrated Object of type specified in RETRIEVED_OBJECT_TYPE
					RETRIEVED_OBJECT_TYPE - Can be JSON, XML, TXT, ERROR
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - JSON status if JSON response is found
					NAME - contains name value if JSON name or xml name data is found
					MESSAGE - JSON message value if JSON response is found
			
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		req = urllib2.Request(url,dataToSend)
		req.get_method = lambda: 'PUT'
		req.add_header('Content-Type', contentType)
		req.add_header("User-Agent", userAgent)
		self.RESPONSE_OBJECT.setRequest(req)
		try:
			buffer = urllib2.urlopen(req)
			data = buffer.read()
			if isJsonResponse:
				try:
					data = simplejson.loads(data)
				except:
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,False)
					return self.RESPONSE_OBJECT
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
			if self.VERBOSE:
				print data
		self.RESPONSE_OBJECT.setRawResponse(data)
		self.processResponse(data,self.RESPONSE_OBJECT,isJsonResponse)
		return self.RESPONSE_OBJECT
	
	def delete(self, url, isJsonResponse = False ,userAgent=USERNAME):
		""" DELETE method used to delte objects on the Mu
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			isJsonResponse - is the response in the form of a json object
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject.
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - contains Mu Job UUID if a UUID is found in XML response from Mu
					JOBID - contains Mu Job ID if a Job Id is found in XML response from Mu
					JSON_OBJCT - conains JSON data if isJsonResponse is true
					RETRIEVED_OBJECT - Fomrated Object of type specified in RETRIEVED_OBJECT_TYPE
					RETRIEVED_OBJECT_TYPE - Can be JSON, XML, TXT, ERROR
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - JSON status if JSON response is found
					NAME - contains name value if JSON name or xml name data is found
					MESSAGE - JSON message value if JSON response is found
			
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		req = urllib2.Request(url)
		req.get_method = lambda: 'DELETE'
		req.add_header("User-Agent", userAgent)
		self.RESPONSE_OBJECT.setRequest(req)
		try:
			buffer = urllib2.urlopen(req)
			data = buffer.read()
			if isJsonResponse:
				try:
					data = simplejson.loads(data)
				except:
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,False)
					return self.RESPONSE_OBJECT
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
     		if self.VERBOSE:
     			print data
		self.RESPONSE_OBJECT.setRawResponse(data)
		self.processResponse(data,self.RESPONSE_OBJECT,isJsonResponse)
		return self.RESPONSE_OBJECT

	def post(self,url,dataToSend=None,contentType='application/x-www-form-urlencoded',isJsonResponse=False,userAgent=USERNAME):
		""" POST method used to send data to the Mu
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			dataToSend - data to send to the Mu normaly in string of JSON or XML data
			contentType - default is empty
			isJsonResponse - is the response in the form of a json object
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject.
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - contains Mu Job UUID if a UUID is found in XML response from Mu
					JOBID - contains Mu Job ID if a Job Id is found in XML response from Mu
					JSON_OBJCT - conains JSON data if isJsonResponse is true
					RETRIEVED_OBJECT - Fomrated Object of type specified in RETRIEVED_OBJECT_TYPE
					RETRIEVED_OBJECT_TYPE - Can be JSON, XML, TXT, ERROR
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - JSON status if JSON response is found
					NAME - contains name value if JSON name or xml name data is found
					MESSAGE - JSON message value if JSON response is found
			
		"""
		if self.VERBOSE:
				print "URL: " + url
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		if dataToSend==None:
			req = urllib2.Request(url)
		else:
			req = urllib2.Request(url, dataToSend)
		req.get_method = lambda: 'POST'
		if contentType==None:
			req.add_header('Content-Type', 'application/x-www-form-urlencoded')
		else:
			req.add_header('Content-Type', contentType)
		req.add_header("User-Agent", userAgent)
		req.add_header("Keep-Alive", 115)
		#if dataToSend == None or dataToSend == '':
		#	req = urllib2.Request(url, dataToSend)
		#	req.get_method = lambda: 'POST'
		#else:
		#	req = urllib2.Request(url,dataToSend)
		#if contentType != None and contentType != '':
			#req.add_header('Content-Type', contentType)
		#	if self.VERBOSE:
		#		print 'add Content-Type: ' + contentType
		#req.add_header("User-Agent", userAgent)
		self.RESPONSE_OBJECT.setRequest(req)
		try:
			if self.VERBOSE:
				print str(req)
			buffer = urllib2.urlopen(req)
			if isJsonResponse:
				try:
					data = buffer.read()
					data = simplejson.loads(data)
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,True)
				except:
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,False)
					return self.RESPONSE_OBJECT
			else:
				data = buffer.read()
				self.RESPONSE_OBJECT.setRawResponse(data)
				self.processResponse(data,self.RESPONSE_OBJECT,False)
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
			if self.VERBOSE:
				print data
			self.RESPONSE_OBJECT.setRawResponse(data)
			self.processResponse(data,self.RESPONSE_OBJECT,False)
		return self.RESPONSE_OBJECT
	
	def postFile(self,url,fileToSend,userAgent=USERNAME,contentType=''):
		""" POST method used to send data from a file to the Mu
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			fileToSend - data to send to the Mu normaly in string of JSON or XML
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			contentType - default is empty
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject.
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - contains Mu Job UUID if a UUID is found in XML response from Mu
					JOBID - contains Mu Job ID if a Job Id is found in XML response from Mu
					JSON_OBJCT - conains JSON data if isJsonResponse is true
					RETRIEVED_OBJECT - Fomrated Object of type specified in RETRIEVED_OBJECT_TYPE
					RETRIEVED_OBJECT_TYPE - Can be JSON, XML, TXT, ERROR
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - JSON status if JSON response is found
					NAME - contains name value if JSON name or xml name data is found
					MESSAGE - JSON message value if JSON response is found
			
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		try:
			fileToSend = open(fileToSend, 'r')
			dataToSend = fileToSend.read()
			req = urllib2.Request(url,dataToSend)
			req.add_header("User-Agent", userAgent)
			req.get_method = lambda: 'POST'
			self.RESPONSE_OBJECT.setRequest(req)
			buffer = urllib2.urlopen(req)
			data = buffer.read()
		except IOError, (errno, strerror):
			data =  "IOError: error " + str(errno) + ": " + strerror
			self.RESPONSE_OBJECT.setError(True)
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
			self.RESPONSE_OBJECT.setError(True)
		self.RESPONSE_OBJECT.setRawResponse(data)
		self.processResponse(data,self.RESPONSE_OBJECT)
		return self.RESPONSE_OBJECT	
		
	def postMime(self, url, dataToPost, mimeFieldName, mimeFieldValue, isJsonResponse=False, isFileNameToLoad = True, userAgent=USERNAME):
		""" POST method used to send data from a file to the Mu in a mime attachment
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			fileToPost - data to send to the Mu normaly in string of JSON or XML
			mimeFieldName - the mime field name for the mime attachment
			mimeFieldValue - the mime filed value for the mime atatchment
			isJsonResponse - is expected repsone in a JSON format
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject.
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - contains Mu Job UUID if a UUID is found in XML response from Mu
					JOBID - contains Mu Job ID if a Job Id is found in XML response from Mu
					JSON_OBJCT - conains JSON data if isJsonResponse is true
					RETRIEVED_OBJECT - Fomrated Object of type specified in RETRIEVED_OBJECT_TYPE
					RETRIEVED_OBJECT_TYPE - Can be JSON, XML, TXT, ERROR
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - JSON status if JSON response is found
					NAME - contains name value if JSON name or xml name data is found
					MESSAGE - JSON message value if JSON response is found
			
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		try:
			form = MultiPartForm()
			# Add a file or data
			if isFileNameToLoad:
				form.add_file(dataToPost, mimeFieldName, mimeFieldValue)
			else:
				form.add_data(dataToPost, mimeFieldName, mimeFieldValue)
			# Build the request
			req = urllib2.Request(url)
			req.add_header('User-agent', userAgent)
			body = str(form)
			req.add_header('Content-type', form.get_content_type())
			req.add_header('Content-length', len(body))
			req.add_data(body)
		
			buffer = urllib2.urlopen(req)
			if isJsonResponse:
				try:
					data = buffer.read()
					data = simplejson.loads(data)
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,True)
					return self.RESPONSE_OBJECT	
				except:
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,False)
					return self.RESPONSE_OBJECT
			else:
				data = buffer.read()
				self.RESPONSE_OBJECT.setRawResponse(data)
				self.processResponse(data,self.RESPONSE_OBJECT,False)
				return self.RESPONSE_OBJECT	
		except IOError, (errno, strerror):
			data =  "IOError: error " + str(errno) + ": " + strerror
			self.RESPONSE_OBJECT.setRawResponse(data)
			self.processResponse(data,self.RESPONSE_OBJECT,False)
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
			self.RESPONSE_OBJECT.setRawResponse(data)
     		self.processResponse(data,self.RESPONSE_OBJECT,False)
		return self.RESPONSE_OBJECT	

	def executeRequest(self,req,isJsonResponse=False):
		""" Used to execute a customized request 
		
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setRequest(req)
		try:
			if self.VERBOSE:
				print str(req)
			buffer = urllib2.urlopen(req)
			if isJsonResponse:
				try:
					data = buffer.read()
					data = simplejson.loads(data)
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,True)
				except:
					self.RESPONSE_OBJECT.setRawResponse(data)
					self.processResponse(data,self.RESPONSE_OBJECT,False)
					return self.RESPONSE_OBJECT
			else:
				data = buffer.read()
				self.RESPONSE_OBJECT.setRawResponse(data)
				self.processResponse(data,self.RESPONSE_OBJECT,False)
		except urllib2.URLError, (err):
			data = "URL error(%s)" % (err)
			if self.VERBOSE:
				print data
			self.RESPONSE_OBJECT.setRawResponse(data)
			self.processResponse(data,self.RESPONSE_OBJECT,False)
		return self.RESPONSE_OBJECT

	def getRequest(self,type,url,dataToSend):
		""" Used to get a created request when customization is needed in the request headers
		
		"""
		if type=='POST':
			if dataToSend==None:
				req = urllib2.Request(url)
			else:
				req = urllib2.Request(url, dataToSend)
		elif type=='GET':
			req = urllib2.Request(url)
		else:
			print 'unknown request method'
			req = None
		return req

	def getRequestBuffer(self, url, method='GET',userAgent=USERNAME,data='',contentType="application/json; charset=utf-8"):
		""" Used to create a request buffer object using opener created in MuConnection
			
			Arguments:
			url - the url used to retrieve data. it is combined with the root url
			method - the http method to be used
			userAgent - the userAgent to assign the reuest to. normaly the same as the username used in the authentication
			data - data to be sent with http method if any is sent beond url request
			contentType - default is "application/json; charset=utf-8"		
			
			Returns:
			urlib2 request buffer from urllib2.urlopen(request)
			
		"""
		self.RESPONSE_OBJECT = MuResponseObject()
		self.RESPONSE_OBJECT.setUrl(url)
		if method == 'GET':
			req = urllib2.Request(url)
			req.add_header("User-Agent", userAgent)
			self.RESPONSE_OBJECT.setRequest(req)
			buffer = urllib2.urlopen(req)
			return buffer
		elif  method == 'PUT':
			req = urllib2.Request(url,data)
			req.get_method = lambda: 'PUT'
			req.add_header('Content-Type', contentType)
			req.add_header("User-Agent", userAgent)
			buffer = urllib2.urlopen(req)
			return buffer
		elif  method == 'POST':
			print "Not IMPLEMENTED: method=POST"
			return
		else:
			print "Not IMPLEMENTED"
			return

	def processResponse(self, rawData, muResponseObject, isJsonResponse=False):
		""" used to process the data returned from the Mu interactions into a MuResponseObject.
			Parsing is done around 3 major groups: JSON, XML, and String. Each type is paresed for 
			common response obejcts that are returned from the Mu API sessions
		
			Arguments:
			rawData - the raw data returned fromt he mu or the HTML error code from an exception
			muResponseObject - the MuResponseObject to store the sorted data in
			isJsonResponse - is the raw data in JSON form
			
			Returns:
			MuResponseObject - Data is retrieved from Mu and stored in MuResponseObject.
					COMPLETED - True if operation completed normaly
					ERROR - False if no error occured
					RAW_RESPONSE - Raw data from HTTP get request or URL error if error is thrown
					UUID - contains Mu Job UUID if a UUID is found in XML response from Mu
					JOBID - contains Mu Job ID if a Job Id is found in XML response from Mu
					JSON_OBJCT - conains JSON data if isJsonResponse is true
					RETRIEVED_OBJECT - Fomrated Object of type specified in RETRIEVED_OBJECT_TYPE
					RETRIEVED_OBJECT_TYPE - Can be JSON, XML, TXT, ERROR
					URL - contains text URL used in creation of urllib2.requst
					REQUST - contains urlib2.requst used in operation
					STATUS - JSON status if JSON response is found
					NAME - contains name value if JSON name or xml name data is found
					MESSAGE - JSON message value if JSON response is found
			
		"""
		muResponseObject.setRawResponse(rawData)
		muResponseObject.setCompleted(False)
		muResponseObject.setError(True)
		# process jason
		if self.VERBOSE:
			print rawData
		if isJsonResponse:
			try:
				for key in rawData:
					if key=="ERROR" or key=="ERROR_NAME" :
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found ERROR'
						muResponseObject.setCompleted(False)
						muResponseObject.setError(True)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
					elif key=="reason":
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found error'
						muResponseObject.setCompleted(False)
						muResponseObject.setError(True)
						message = rawData['reason']
						muResponseObject.setStatus(False)
						muResponseObject.setMessage(message)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
					elif key=="sessions":
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found sessions'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
					elif key=='response':
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found response'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						data = rawData['response']
						message = data['message']
						status = data['status']
						sessionId = data['sessionId']
						muResponseObject.setMessage(message)
						muResponseObject.setStatus(status)
						return muResponseObject
					elif key=='hosts':
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found hosts'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
					elif key=='options':
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found options'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
					elif key=='channels':
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found channels'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
					elif key=='testResult':
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found testResult'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						data = rawData['testResult']
						status = data['status']
						muResponseObject.setStatus(status)
						if status == "Failed":
							message = data['testResult']
							muResponseObject.setMessage(message)
						break
					elif key=='testResults':
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found testResults'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.JSON)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
					elif key=='statistics':
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found statistics'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.SCALE_STATS)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setJsonObject(rawData)
						break
				# exit for for loop if no break
				else:
					if self.VERBOSE:
						print 'MuConnection: in processResponse: no break found in loop through JSON keys'
					muResponseObject.setCompleted(True)
					muResponseObject.setError(False)
					muResponseObject.setRetrievedObjectType("UNKNOWN_JSON_RESPONSE")
					muResponseObject.setRetrievedObject(rawData)
					muResponseObject.setJsonObject(rawData)
			except:
				if self.VERBOSE:
					print 'MuConnection: in processResponse: exception thrown in json parsing'
				muResponseObject.setCompleted(True)
				muResponseObject.setError(False)
				muResponseObject.setRetrievedObjectType("UNKNOWN_JSON_EXCEPTION")	
		else:
			try:
				document = parseString(rawData)
				root = document.documentElement
				muResponseObject.setRawResponse(rawData)
				if root.tagName == 'job':
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found job xml tag'
					muResponseObject.setJobId(root.getAttribute('id'))
					muResponseObject.setName(root.getAttribute('name'))
					myTree = ElementTree.XML(rawData)
					for element in myTree:
						if element.tag == "status":
							muResponseObject.setStatus(element.text)
							muResponseObject.setCompleted(True)
							muResponseObject.setError(False)
				elif root.tagName == 'analysis':
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found analysis xml tag'
					muResponseObject.setRetrievedObjectType(muResponseObject.XML)
					muResponseObject.setUUID(root.getAttribute('uuid'))
					myTree = ElementTree.XML(rawData)
					for element in myTree:
						if element.tag == "status":
							muResponseObject.setStatus(element.text)
						elif element.tag == "name":
							muResponseObject.setName(element.text)
					muResponseObject.setCompleted(True)
					muResponseObject.setError(False)
				elif root.tagName == 'import_summary':
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found import_summary xml tag'
					myTree = ElementTree.XML(rawData)
					for element in myTree:
						if element.tag == "model_object":
							childList = element.getchildren()
							for element2 in childList:
								if element2.tag == "type":
									muResponseObject.setStatus(element2.text)
								elif element2.tag == "name":
									muResponseObject.setName(element2.text)
								elif element2.tag == "uuid":
									muResponseObject.setUUID(element2.text)
							muResponseObject.setCompleted(True)
							muResponseObject.setError(False)
				elif root.tagName == 'analysis_list':
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found analysis_list xml tag'
					muResponseObject.setCompleted(True)
					muResponseObject.setError(False)
					muResponseObject.setRetrievedObjectType(muResponseObject.ANALYSIS_LIST)
					muResponseObject.setRetrievedObject(rawData)
				elif root.tagName == 'fault_list_summary':
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found fault_list_summary xml tag'
					muResponseObject.setCompleted(True)
					muResponseObject.setError(False)
					muResponseObject.setRetrievedObjectType(muResponseObject.FAULT_LIST_SUMMARY)
					muResponseObject.setRetrievedObject(rawData)
				else:
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found UNKOWN xml tag\n'+rawData
					muResponseObject.setCompleted(False)
					muResponseObject.setError(True)
			except:
				if isinstance(rawData, str):
					if rawData.find("URL error")>=0:
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found URL error text '
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setRetrievedObjectType(muResponseObject.URL_ERROR)
						muResponseObject.setCompleted(False)
						muResponseObject.setError(True)	
					elif (rawData.find("updated")>=0) or (rawData.find("created")>=0) or (rawData.find("deleted")>=0):
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found basic response text: updated, created, or deleted'
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.TEXT)
						muResponseObject.setRetrievedObject(rawData)
					elif muResponseObject.getUrl().find("/templates/types")>=0:
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						#muResponseObject.setRetrievedObjectType(muResponseObject.TEXT_ARAY)
						#tmpList = rawData.split("\n")
						#muResponseObject.setRetrievedObject(tmpList)
					elif muResponseObject.getUrl().startswith('["')>=0:
						muResponseObject.setCompleted(True)
						muResponseObject.setError(False)
						muResponseObject.setRetrievedObjectType(muResponseObject.TEXT)
						muResponseObject.setRetrievedObject(rawData)
						muResponseObject.setRawResponse(rawData)
					else:
						if self.VERBOSE:
							print 'MuConnection: in processResponse: found unknown response or response id not implemeted'
							print 'Raw Response: ' + rawData
						muResponseObject.setCompleted(False)
						muResponseObject.setError(True)
						muResponseObject.setRetrievedObjectType("UNKNOWN")
						muResponseObject.setRetrievedObject(rawData)
				elif isinstance(rawData, MuResponseObject):
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found MuResponseObject'
					#muResponseObject.getUrl().find("TemplateTypes")>=0:
					muResponseObject.setCompleted(True)
					muResponseObject.setError(False)
					muResponseObject.setRetrievedObjectType(muResponseObject.TEXT_ARAY)
					tmpList = rawData.split("\n")
					muResponseObject.setRetrievedObject(tmpList)
				else:
					if self.VERBOSE:
						print 'MuConnection: in processResponse: found unknown object response or response object is not implemeted'
						#print rawData.instattr
					muResponseObject.setCompleted(False)
					muResponseObject.setError(True)
					muResponseObject.setRetrievedObjectType("UNKNOWN")
					muResponseObject.setRetrievedObject(rawData)
		return muResponseObject
		
	

class MuResponseObject():
	""" MuResponseObject: This class is instantiated to store data returned from a Mu API session. 
			It is meant to insolate any Automation development by Mu users from changes in the raw Mu API.

		Arguments: 
		None

		Globals:
		---------------------------------------------------------------------
		These variables are constants used for defining RETRIEVED_OBJECT_TYPE
		JSON = "JSON" - JSON formated object
		FILE = "FILE" - File name including path
		XML = "XML" - XML string data
		TEXT = "TEXT" - String object
		TEXT_ARAY = "TEXT_ARAY" - List of strings
		URL_ERROR = "URL_ERROR" - string data indicating what URL Error was thrown
		ANALYSIS_LIST = list of XML analysis responses in one string object
		FAULT_LIST_SUMMARY = "FAULT_LIST_SUMMARY" - list of Faults in XML fomr in one strng object
		FAULT_LIST = "FAULT_LIST" - list of MuRestApi.Fault objects
		RESULTS_LIST = "RESULTS_LIST" - list of MuTestApi.Result objects
		LIST = "LIST" - a pyhton list of objects
		---------------------------------------------------------------------
		These variables are used for storing data returned from the Mu API session
		COMPLETED - boolan indicating if the operation completed with expected result
		ERROR - boolean indicating if an error occured in the operation
		RAW_RESPONSE - raw data or URL error returned from Mu API session
		UUID - uuid from returned json or xml data
		JOBID - jobid from returned json or xml data
		JSON_OBJCT - json object returned from Mu api session
		RETRIEVED_OBJECT - formated object normaly created from the data contained in the RAW_RESPONSE
		RETRIEVED_OBJECT_TYPE - Type of data to be found in RETRIEVED_OBJECT
		URL - url used in the Mu API operation
		REQUST - request used in the urllib2 operation
		STATUS - status value returned in the JSON or XML data
		NAME - name value specified in the JSON or XML data 
		MESSAGE - a JSON message response value if one is found
		---------------------------------------------------------------------
	
		TODO:
	
	"""

	def __str__(self):
		""" used to print MuResponseObject in some maner of order
			
			Arguments:
			none
			
			Returns:
			String
			
		"""
		data = "COMPLETED = " + str(self.COMPLETED)  + "\n"
		data = data + "ERROR = " + str(self.ERROR)  + "\n"
		data = data + "STATUS = " + str(self.STATUS)  + "\n"
		data = data + "MESSAGE = " + str(self.MESSAGE)  + "\n"
		data = data + "NAME = " + self.NAME  + "\n"
		data = data + "UUID = " + self.UUID  + "\n"
		data = data + "JOBID = " + self.JOBID  + "\n"
		data = data + "JSON_OBJCT = " + str(self.JSON_OBJCT)  + "\n"
		data = data + "RETRIEVED_OBJECT = " + str(self.RETRIEVED_OBJECT)  + "\n"
		data = data + "RETRIEVED_OBJECT_TYPE = " + self.RETRIEVED_OBJECT_TYPE  + "\n"
		data = data + "URL = " + self.URL  + "\n"
		data = data + "REQUST = " + str(self.REQUST)  + "\n"
		return data

	#Object types recieved
	JSON = "JSON"
	FILE = "FILE"
	XML = "XML"
	#HTTP_ERROR = "HTTP_ERROR" #delete later as i think this is not used
	TEXT = "TEXT"
	TEXT_ARAY = "TEXT_ARAY"
	URL_ERROR = "URL_ERROR"
	ANALYSIS_LIST = "ANALYSIS_LIST"
	FAULT_LIST_SUMMARY = "FAULT_LIST_SUMMARY"
	FAULT_LIST = "FAULT_LIST"
	RESULTS_LIST = "RESULTS_LIST"
	LIST = "LIST"
	SCALE_STATS = "SCALE_STATS"
	
	# Contents
	COMPLETED = False
	ERROR = False
	RAW_RESPONSE = ''
	UUID = ''
	JOBID = ''
	JSON_OBJCT = ''
	RETRIEVED_OBJECT = []
	RETRIEVED_OBJECT_TYPE = ''
	URL = ''
	REQUST = ''
	STATUS = None
	NAME = ''
	MESSAGE = ''
	
	
	#------ Gets --------
	def getRawResponse(self):
		return self.RAW_RESPONSE
	
	def getRetrievedObject(self):
		return self.RETRIEVED_OBJECT
		
	def getRetrievedObjectType(self):
		return self.RETRIEVED_OBJECT_TYPE
		
	def getJsonObject(self):
		return self.JSON_OBJCT
		
	def getUrl(self):
		return self.URL
	
	def getRequest(self):
		return self.REQUST
		
	def getUUID(self):
		return self.UUID
		
	def getJobid(self):
		return self.JOBID
		
	def isCompleted(self):
		return self.COMPLETED
		
	def hasError(self):
		return self.ERROR
		
	def getStatus(self):
		return self.STATUS
		
	def getName(self):
		return self.NAME
	
	def getMessage(self):
		return self.MESSAGE
		
	#------ Sets --------
	def setRawResponse(self, obj):
		self.RAW_RESPONSE = obj 
	
	def setUUID(self, obj):
		self.UUID = obj
		
	def setJobId(self, obj):
		self.JOBID = obj
		
	def setRetrievedObject(self, obj):
		self.RETRIEVED_OBJECT = obj
		
	def setRetrievedObjectType(self, obj):
		self.RETRIEVED_OBJECT_TYPE = obj
		
	def setJsonObject(self, obj):
		self.JSON_OBJCT = obj
		
	def setUrl(self, obj):
		self.URL = obj
	
	def setRequest(self, obj):
		self.REQUST = obj
	
	def setCompleted(self, obj):
		self.COMPLETED = obj
		
	def setError(self, obj):
		self.ERROR = obj
		
	def setStatus(self, obj):
		self.STATUS = obj
		
	def setName(self, obj):
		self.NAME = obj
		
	def setMessage(self, obj):
		self.MESSAGE = obj


class MultiPartForm(object):
	""" Accumulate the data to be used when posting a form for mime attachment. 
		The code below was found on an anonymous posting on a web site that I cant remember 
		so thank you to whomever wrote it.
	"""


	def __init__(self):
		self.form_fields = []
		self.files = []
		self.boundary = mimetools.choose_boundary()
		return

	def get_content_type(self):
		return 'multipart/form-data; boundary=%s' % self.boundary


	def add_field(self, name, value):
		"""Add a simple field to the form data."""
		self.form_fields.append((name, value))
		return


	def add_file(self, fileName, fieldName, fieldValue, mimetype=None):
		"""Add a file to be uploaded."""
		fileToSend = open(fileName, 'r')
		body = fileToSend.read()
		if mimetype is None:
			mimetype = mimetypes.guess_type(fileName)[0] or 'application/octet-stream'
		self.files.append((fieldName, fieldValue, mimetype, body))
		return True
		
	def add_data(self, data, fieldName, fieldValue, mimetype=None):
		"""Add a data to be uploaded."""
		body = data
		if mimetype is None:
			mimetype = mimetypes.guess_type(data)[0] or 'application/octet-stream'
		self.files.append((fieldName, fieldValue, mimetype, body))
		return True

	def __str__(self):
		"""Return a string representing the form data, including attached files."""
		# Build a list of lists, each containing "lines" of the
		# request. Each part is separated by a boundary string.
		# Once the list is built, return a string where each
		# line is separated by '\r\n'.
		parts = []
		part_boundary = '--' + self.boundary

		# Add the form fields
		parts.extend(
			[ part_boundary,
				'Content-Disposition: form-data; name="%s"' % name,
				'',
				value,
			]
			for name, value in self.form_fields
			)

		# Add the files to upload
		parts.extend(
			[ part_boundary,
				'Content-Disposition: file; name="%s"; filename="%s"' % \
				(field_name, filename),
				'Content-Type: %s' % content_type,
				'',
				body,
			]
			for field_name, filename, content_type, body in self.files
			)

		# Flatten the list and add closing boundary marker,
		# then return CR+LF separated data
		flattened = list(itertools.chain(*parts))
		flattened.append('--' + self.boundary + '--')
		flattened.append('')
		return '\r\n'.join(flattened)


